package img;

import com.google.common.collect.Sets;
import com.idrsolutions.image.png.PngCompressor;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.util.Set;

/**
 * 图片压缩工具类：暂时只能压缩png和jpg，不能压缩gift
 *
 * 如果图片库所占空间大，或者保密性高，都不适合使用在线的图片压缩工具，只能自己写一个
 * https://gitee.com/yt2018_admin/image-host
 * jpg的压缩使用开源组件thumbnailator，png压缩使用OpenViewerFX中的PngCompressor
 *
 * https://tinypng.com/ 图片数量限制20，有提供java sdk，需要注册获取key https://tinypng.com/developers/reference/java
 * Java 多线程拷贝文件夹并调用tinyPng算法接口压缩图片实现（生产消费变种） https://www.cnblogs.com/cbam/p/11816514.html
 * https://github.com/Deemonser/TinyPngPlugin 利用 TinyPNG 的 API ，开发的图片压缩工具
 * https://github.com/hocgin/ZiPng PNG/JPEG 压缩工具, 使用Tiny
 *
 * 使用此工具压缩jpg图片失败，原因是代码将所有图片都保存为了jpg格式，压缩失败的图片本质上其实是png
 * 所以需要引入魔数来判断图片的真正格式
 */
public class ImgCompressUtil {

    // png图片 格式类型的集合
    private static final Set<String> PNG_EXTS = Sets.newHashSet(".png");
    // jpg图片 格式类型的集合
    private static final Set<String> JPG_EXTS = Sets.newHashSet(".jpg", ".jpeg");
    // 所有图片类型
    private static final Set<String> IMG_EXTS = Sets.newHashSet();

    // 压缩之后的文件是直接替换，还是生成副本 在原文件夹 加-1
    private static final boolean IS_COVER = false;

    static {
        IMG_EXTS.addAll(PNG_EXTS);
        IMG_EXTS.addAll(JPG_EXTS);
    }

    /**
     * 图片大小超过多少才进行压缩
     * 1048576L 1m以上
     * 314572L 300k以上
     */
    private static final long MAX_SIZE = 314572L; // 单位是bytes

    /**
     * 图片质量
     */
    private static final float QUALITY = 0.3f;

    public static void main(String[] args) throws Exception {
        // 要压缩的目录 或文件
        String filePath = "D:\\UPLOAD\\23408";

        FileUtil.scanOrHandleFile(filePath, f -> toCompress(f));
    }

    private static void toCompress(File file) {
        // 文件后缀
        String fileExt = FileUtil.getFileExtWithDoc(file);

        if (fileExt != null && IMG_EXTS.contains(fileExt)) {
            // 是否是图片 jpg或者png
            try {
                doCompress(file, fileExt);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("非图片类型：" + file.getAbsolutePath());
        }
    }

    private static void doCompress(File sourceFile, String fileExt) throws Exception {
        // 超过指定大小才进行压缩
        if (sourceFile.length() < MAX_SIZE) {
            return;
        }

        byte[] result = null;
        // 检查实际类型
        String mimeType = FiletypeUtils.getMimeType(sourceFile.getAbsolutePath());

        if (PNG_EXTS.contains(fileExt)) {
            // png
            if (StringUtils.isBlank(mimeType) || ".png".equals(mimeType)) {
                result = compressPng(sourceFile);
            } else {
                if (JPG_EXTS.contains(mimeType)) {
                    // 换正确的方法来压缩
                    System.out.println("压缩png-换正确的方法来压缩：图片文件后缀被修改，实际类型：" + mimeType + "，图片路径：" + sourceFile.getAbsolutePath());
                    result = compressJpg(sourceFile);
                } else {
                    System.out.println("压缩png失败：图片文件后缀被修改，实际类型：" + mimeType + "，图片路径：" + sourceFile.getAbsolutePath());
                }
            }
        } else {
            // jpg
            if (StringUtils.isBlank(mimeType) || JPG_EXTS.contains(mimeType)) {
                result = compressJpg(sourceFile);
            } else {
                if (PNG_EXTS.contains(mimeType)) {
                    // 换正确的方法来压缩
                    System.out.println("压缩jpg-换正确的方法来压缩：图片文件后缀被修改，实际类型：" + mimeType + "，图片路径：" + sourceFile.getAbsolutePath());
                    result = compressPng(sourceFile);
                } else {
                    System.out.println("压缩jpg失败：图片文件后缀被修改，实际类型：" + mimeType + "，图片路径：" + sourceFile.getAbsolutePath());
                }
            }
        }

        if (result != null) {
            File outFile = getOutFile(sourceFile);
            System.out.println("压缩文件成功：" + outFile.getAbsolutePath());
            FileUtils.writeByteArrayToFile(outFile, result);
        }
    }

    /**
     * 压缩jpg
     * @param srcFile
     * @return
     * @throws Exception
     */
    private static byte[] compressJpg(File srcFile) throws Exception {
        byte[] srcBytes = FileUtils.readFileToByteArray(srcFile);

        try (InputStream srcIn = new ByteArrayInputStream(srcBytes);
             ByteArrayOutputStream byteOut = new ByteArrayOutputStream()) {

            Thumbnails.of(srcIn).scale(1f).outputQuality(QUALITY).toOutputStream(byteOut);
            return byteOut.toByteArray();
        } catch (Exception e) {
            System.out.println("jpg-压缩图片文件出错，将返回原图片流!");
            return srcBytes;
        }
    }

    /**
     * 压缩png
     * @param srcFile
     * @return
     * @throws Exception
     */
    private static byte[] compressPng(File srcFile) throws Exception {
        byte[] datas = FileUtils.readFileToByteArray(srcFile);
        try (InputStream srcIn = new ByteArrayInputStream(datas);
             ByteArrayOutputStream byteOut = new ByteArrayOutputStream()) {

            PngCompressor.compress(srcIn, byteOut);
            return byteOut.toByteArray();
        } catch (Exception e) {
            System.out.println("png-压缩图片文件出错，将返回原图片流!");
            return datas;
        }
    }

    /**
     * 获取压缩之后输出的文件路径
     */
    private static File getOutFile(File sourceFile) {
        String parentDirectory = sourceFile.getParent();
        String fileName = sourceFile.getName();
        int loc = fileName.lastIndexOf(".");
        // 后缀 带.
        String suffix = fileName.substring(loc);
        // 名称
        String name = fileName.substring(0, loc);
        // 输出文件路径
        String outPath = null;
        if (IS_COVER) {
            // 覆盖原图
            outPath = parentDirectory + File.separator + name + suffix;
        } else {
            // 副本路径
            outPath = parentDirectory + File.separator + name + "-1" + suffix;
        }

        File outFile = new File(outPath);
        return outFile;
    }

}
